package gen

import (
	"excel2lua/meta"
	"fmt"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"unicode"

	"github.com/samber/lo"
)

type LuaFile struct {
	ClassName string
	StoreName string
}

type LuaGenerator struct {
	dir         string
	initialized bool
	files       []LuaFile
	store       map[string]meta.DataStore
	ignores     []string
}

func (lg *LuaGenerator) initialize() Generator {
	if !lg.initialized {
		if len(lg.dir) == 0 {
			lg.dir = "lua"
		}
		_ = os.RemoveAll(lg.dir)
		_ = os.MkdirAll(lg.dir, 0644)
		lg.initialized = true
		fmt.Println("[LuaGenerator] ------------------------------------------------------")
	}
	return lg
}

func (lg *LuaGenerator) Gen() {
	lg._ProcessRef()
	//--------------------------------------------------------------------
	fmt.Println("\t[GenLua] ------------------------------------------------------")
	for className, ObjMap := range lg.store {
		if lo.IndexOf(lg.ignores, className) > -1 || len(ObjMap.List) == 0 {
			continue
		}
		fmt.Printf("\t\t[%s]\n", className)
		if err := lg._Gen(className, ObjMap); err != nil {
			fmt.Printf("\t\t\t%s\n", err.Error())
		}
	}
	//--------------------------------------------------------------------
	if err := lg._GenManager(); err != nil {
		fmt.Printf("\t\t%s\n", err.Error())
	}
}

func (lg *LuaGenerator) _Gen(className string, Store meta.DataStore) error {
	// is number id
	// is string id
	var dsName string = fmt.Sprintf("%sDataStore", className)
	var lines []string = []string{fmt.Sprintf("local %s = {", dsName)}
	var fileName string = fmt.Sprintf("%s.lua", dsName)
	for _, obj := range Store.List {
		if strings.IndexFunc(obj.Key, func(r rune) bool { return !unicode.IsDigit(r) }) != -1 { // string id
			lines = append(lines, fmt.Sprintf("\t['%s'] = %s,", obj.Key, lg.map2lua(className, obj.Object)))
		} else {
			lines = append(lines, fmt.Sprintf("\t[%s] = %s,", obj.Key, lg.map2lua(className, obj.Object)))
		}
		// fmt.Printf("\t[%s] = %s\n", objKey, map2lua(obj))
	}
	// delete last rune
	lines[len(lines)-1] = lines[len(lines)-1][:len(lines[len(lines)-1])-1]
	//
	lines = append(lines, "}")
	lines = append(lines, fmt.Sprintf("return %s", dsName))
	data := strings.Join(lines, "\n")
	err := os.WriteFile(filepath.Join(lg.dir, fileName), []byte(data), 0644)
	if err != nil {
		return fmt.Errorf("[LuaGenerator] Error writing %s file: %v", fileName, err)
	}
	lg.files = append(lg.files, LuaFile{ClassName: className, StoreName: dsName})
	sort.Slice(lg.files, func(i, j int) bool { return lg.files[i].ClassName < lg.files[j].ClassName })
	// fmt.Printf("%s\n", strings.Join(lines, "\n"))
	return nil
}

func (lg *LuaGenerator) _GenObjectRow(className string, rowType RowType, rowKey string, obj map[string]string) string {
	if rowType == Array {
		return lg.map2lua(className, obj)
	} else if rowType == Map {
		return fmt.Sprintf("['%s'] = %s", rowKey, lg.map2lua(className, obj))
	} else {
		return "{}"
	}
}

func (lg *LuaGenerator) _ProcessRef() {
	fmt.Println("\t[ProcessRef] ------------------------------------------------------")
	for className, data := range lg.store {
		if len(data.List) == 0 {
			continue
		}
		////
		fmt.Printf("\t\t[%s]\n", className)
		for _, obj := range data.List {
			obj.Object = lg.processRef(obj.Object)
		}
	}
}

func (lg *LuaGenerator) processRef(obj map[string]string) map[string]string {
	for prop, propValue := range obj {
		isRef := strings.Contains(propValue, "ref_list") || strings.Contains(propValue, "ref_map")
		if !isRef {
			continue
		}
		segs := strings.Split(strings.TrimFunc(propValue, func(r rune) bool { return r == '\'' }), ":")
		if len(segs) != 3 || len(segs[2]) == 0 {
			(obj)[prop] = "{}"
		} else {
			ref, ClassName, list := segs[0], segs[1], segs[2]
			objList := strings.Split(list, ",")
			var objects []string
			for _, objKey := range objList {
				var obj map[string]string
				var err error
				if obj, err = lg.getObj(ClassName, objKey); err != nil {
					fmt.Printf("\t\t\t[%s] %s\n", propValue, err.Error())
				}
				if ref == "ref_list" || ref == "ref" {
					objects = append(objects, lg._GenObjectRow(ClassName, Array, objKey, obj))
				} else if ref == "ref_map" {
					objects = append(objects, lg._GenObjectRow(ClassName, Map, objKey, obj))
				}
			}
			(obj)[prop] = "{" + strings.Join(objects, ", ") + "}"
		}
	}
	return obj
}

func (lg *LuaGenerator) getObj(ClassName, ObjKey string) (map[string]string, error) {
	if Store, ok := lg.store[ClassName]; ok {
		if obj, ook := lo.Find(Store.List, func(it meta.StoreItem) bool { return it.Key == ObjKey }); ook {
			return lg.processRef(obj.Object), nil
		} else {
			return nil, fmt.Errorf("[%s] not found object. key = %s", ClassName, ObjKey)
		}
	} else {
		return nil, fmt.Errorf("[%s] not found object store. class = %s", ClassName, ClassName)
	}
}

func (lg *LuaGenerator) _GenManager() error {
	fmt.Println("\t[DataStoreManager] ------------------------------------------------------")
	var dsm []string = []string{"local Manager = {}\n"}
	for _, lf := range lg.files {
		dsm = append(dsm, fmt.Sprintf(`
------------------------------------------------ %sStore ------------------------------------------------------------
local %sStore = require('cfg.lua.%s')
function Manager:Get%s(Id)
	return %sStore[Id]
end
function Manager:GetAll%s()
	return %sStore
end
`, lf.ClassName,
			lf.ClassName, lf.StoreName,
			lf.ClassName, lf.ClassName,
			lf.ClassName, lf.ClassName))
	}
	dsm = append(dsm, "\nreturn Manager")
	if err := os.WriteFile(filepath.Join(lg.dir, "DataStoreManager.lua"), []byte(strings.Join(dsm, "")), 0644); err != nil {
		return fmt.Errorf("[LuaGenerator] Error writing DataStoreManager.lua: %v", err)
	}
	return nil
}

func (lg *LuaGenerator) _GetHeaders(className string) []meta.Header {
	return lg.store[className].Headers
}

func (lg *LuaGenerator) map2lua(className string, obj map[string]string) string {
	if obj == nil {
		return "{}"
	}
	var headers = lg._GetHeaders(className)
	var props []string
	for _, v := range headers {
		props = append(props, fmt.Sprintf("['%s'] = %s", v.Name, obj[v.Name]))
	}
	// for k, v := range obj {
	// 	props = append(props, fmt.Sprintf("['%s'] = %s", k, v))
	// }
	return "{" + strings.Join(props, ", ") + "}"
}
